package roborally;
import be.kuleuven.cs.som.annotate.*;

/**
 * 
 * @invar 
 * 
 * @author Helena & Giuseppe
 *
 */

public class Robot implements IRobot {



	private double energyAmount;

	/**
	 * constructor of the class Robot to create a Robot
	 * 
	 * @param x
	 * 			The x-coordinate of the new robot
	 * @param y
	 * 			The y-coordinate of the new robot
	 * @param orientation
	 * 			The orientation of the new robot
	 * @param energy
	 * 			The energy amount of the new robot
	 * @effect the position of the robot is set to the given x- & y-coordinate on the board
	 * 		|setPosition(x, y)
	 * @effect the orientation of the robot is set to the given direction
	 * 		|setOrienation(orientation)
	 * @effect the amount of energy of the robot is set to the given amount
	 * 		|setEnergyAmount(energy)
	 * 
	 */

	//[WEGDOEN] met constructor werken of een voor een de methods oproepen?
	public Robot(long x, long y, int orientation, double energy){
		setPosition(x, y); //[WEGDOEN] mag dit? of moet het this.coordinates[0]=x...? throw exc
		setOrientation(orientation);
		setEnergyAmount(energy);
	}
	/**
	 * return the amount of energy left for the robot
	 * 
	 * @return amount of energy left
	 * 			| result == this.energyAmount
	 * 
	 */

	@Basic
	public double getEnergyAmount(){
		return this.energyAmount;
	}

	/**
	 * Checks whether the amount of energy of the robot is not above the maximum energy level that's allowed
	 * 
	 * @return 	true if and only if the amount of energy of the robot is more or equal to 0 and if the amount of energy of the robot does not exceed the maximum amount of energy
	 * 			|energyAmount >= 0 && energyAmount <= MAX_AMOUNT_OF_ENERGY
	 */

	public boolean isValidEnergyAmount(double energyAmount){
		return energyAmount >= 0 && energyAmount <= MAX_AMOUNT_OF_ENERGY;
	}

	public final static int MAX_AMOUNT_OF_ENERGY = 20000;

	/**
	 *
	 * return the amount of energy as a fraction of the maximum amount of energy
	 * 
	 * @return 	returns the amount of energy left in a percentage
	 * 			|result == (this.energyAmount / MAX_AMOUNT_OF_ENERGY) * 100 
	 */

	public double getFractionAmount(){
		return (this.energyAmount / MAX_AMOUNT_OF_ENERGY) * 100;
	}

	/**
	 * recharge the robot with a certain amount of energy
	 * 
	 * @pre The given amount of energy must be positive
	 * 		| amountToAdd > 0
	 * 
	 * @param amountToAdd
	 * 			The amount of energy that needs to be added to the current amount of energy of the robot
	 * 
	 * @effect the new amount of energy is set to the old amount of energy plus the amount that you want to add 
	 * 		|new.getAmountOfEnergy() == this.getAmountOfEnergy() + amountToAdd
	 */

	public void recharge(int amountToAdd){
		assert amountToAdd > 0;
		setEnergyAmount(getEnergyAmount() + amountToAdd);
	}

	/**
	 * sets the amount of energy that the robot has
	 * 
	 * @param amount
	 * 
	 * @pre the given amount of energy must be a valid amount of energy for the robot and also taking the maximum allowed amount of energy into account
	 * 		|isValidEnergyAmount(energyAmount)
	 *
	 * @post The amount of energy of the robot is equal to the given amount of energy (that it's going to get)
	 * 			| new.getEnergyAmount() == energyAmount
	 * 
	 */

	private void setEnergyAmount(double energyAmount){
		assert isValidEnergyAmount(energyAmount);
		this.energyAmount = energyAmount;
	}

	/**
	 * turns the robot 90 degrees clockwise or counterclockwise
	 * 
	 * @param direction
	 * 		The direction in which you want to move the robot
	 * @post if the given direction is Left, the robot should turn 90 degrees to the left and the amount of energy that's required for turning is subtracted from the energy amount
	 * 		|if( direction == Turning.Left) then
	 * 		|new.getOrientation == this.getOrientation() - 1
			|new.getEnergyAmount == this.getEnergyAmount()-LOST_ENERGY_TURNING
	 * @post if the given direction is Right, the robot should turn 90 degrees to the right and the amount of energy that's required for turning is subtracted from the energy amount
	 * 		|if(direction == Turning.Right) then
	 * 		|new.getOrientation == this.getOrientation() + 1		
	 *		|new.getEnergyAmount == getEnergyAmount()-LOST_ENERGY_TURNING
	 * 
	 */

	//[WEGDOEN] subtracten --> aparte postconditie

	public void turnQuarter(Turning direction){ //[WEGDOEN] enum gebruiken?--> Assistent vragen
		// indien enum, methode naam veranderen in TurnQuarter
		if (direction == Turning.Left){
			setOrientation(getOrientation() - 1);
			setEnergyAmount(getEnergyAmount()-LOST_ENERGY_TURNING);

		} 
		//else{ [AANGEPAST]
		if(direction == Turning.Right){
			setOrientation(getOrientation() + 1);			
			setEnergyAmount(getEnergyAmount()-LOST_ENERGY_TURNING);
		}
		//}
		//[AANGEPAST]

	}


	public final static int LOST_ENERGY_TURNING=100;

	/**
	 * steps forward in the given direction
	 * 
	 * @post The robot has moved forward one step in the direction it's facing 
	 * 		|new.getXCoord() == getXCoord() + getIncrementationXCoordUsingMove()
	 * 		|new.getYCoord() == getYCoord() + getIncrementationYCoordUsingMove()
	 * 
	 * @effect the amount of energy of the robot is set to the amount of energy decremented with amount of energy needed to move
	 *  
	 */

	public void move(){
		try{
			setPosition(getXCoord()+getIncrementationXCoordUsingMove(),getYCoord()+getIncrementationYCoordUsingMove());
			setEnergyAmount(getEnergyAmount()-LOST_ENERGY_MOVE);
		}
		catch(OutOfBoardException exc){
			setPosition(getXCoord(), getYCoord());
		}

	}

	//[AANGEPAST] 

	private long getIncrementationXCoordUsingMove(){
		return (2-getOrientation())%2;
	}

	//[AANGEPAST]

	private long getIncrementationYCoordUsingMove(){
		return -(1-getOrientation())%2;
	}
	
	//Hieronder zijn enkele alternatieven geimplementeerd die de incrementaties teruggeven op een toch wel overduidelijke manier
//	private long getIncrementationYCoordUsingMove(){
//		if (getOrientation()==0)
//			return -1;
//		if (getOrientation()==2)
//			return 1;
//		return 0;
//		
//	}
//
//	private long getIncrementationXCoordUsingMove(){
//		if (getOrientation()==1)
//			return 1;
//		if (getOrientation()==3)
//			return -1;
//		return 0;
//	}



	public final static int LOST_ENERGY_MOVE=500;
	//NOGDOEN
	/**
	 * 
	 * returns the amount that's needed to move to a given position
	 * 
	 * @param desiredPosition
	 * 
	 * @return amountOfEnergyNeeded
	 * @note		|
	 */

 	public double getAmountNeededToReach(long xCoordDestPosition, long yCoordDestPosition){ //stel op dezelfde positie?
		int timesTurning=0;
		long diffXDest=xCoordDestPosition- getXCoord();
		long diffYDest=yCoordDestPosition- getYCoord();

		if(diffXDest != 0 && diffYDest != 0){

			if (isDirectionEqualToAnEndOrientation(diffXDest, diffYDest)){
				timesTurning += 1;
			}
			else{
				timesTurning += 2;
			}
		}
		
		else{
			
			if(isDirectionOppositeEndDirectionAndEqualAxis(diffXDest, diffYDest )){
				timesTurning += 2;
			}
			
			if(!isDirectionEqualToAnEndOrientation(diffXDest, diffYDest)
					&&!isDirectionOppositeEndDirectionAndEqualAxis(diffXDest, diffYDest )){
				timesTurning += 1;
			}
			

		}
		
		return (Math.abs(diffYDest) + Math.abs(diffXDest))*LOST_ENERGY_MOVE + timesTurning*LOST_ENERGY_TURNING;
		
		
//		if (diffXDes != 0 && diffYDes != 0){ //sowieso 1 keer
//			timesTurning++;
//			
//			if (diffXDes > 0  && diffYDes >0  && (getOrientation() == 2 ||getOrientation() == 3 )){ //in de war met y
//				timesTurning++;
//			}
//			
//			if (diffXDes < 0  && diffYDes < 0  && (getOrientation() == 0 ||getOrientation() == 1 )){ // de twee in 1 if?
//				timesTurning++;
//			}
//		}
//		
//		if (diffXDes != 0 && diffYDes == 0 ||diffXDes == 0 && diffYDes != 0){
//			if (diffXDes > 0 && getOrientation()== 3 ||diffYDes > 0 && getOrientation()== 2 || diffXDes < 0 && getOrientation()== 1 ||diffYDes < 0 && getOrientation()== 0){
//				timesTurning++;
//				timesTurning++;
//			}
//			if ((diffXDes != 0 && (getOrientation()==0 || getOrientation()==2))||(diffYDes != 0 && (getOrientation()==1 || getOrientation()==3))){
//				timesTurning++;
//			}
//		}
//		
	
		//Bovenstaande implementatie kan versimpeld worden dmv getters die booleanwaarden teruggeven (voor verschillende situaties)
		//boven is maar schets, maar werkt
	}
//TODO doc
 	private boolean isDirectionEqualToAnEndOrientation(long diffXDest, long diffYDest ){
 		return (diffXDest>0 && getOrientation()==1 
 				 || diffXDest<0 && getOrientation()==3 
 				 || diffYDest>0 && getOrientation()==2 
 				 || diffYDest<0 && getOrientation()==0)
 				||(diffYDest==0 && diffXDest==0);
 		
 	}
 //TODO doc	
 	private boolean isDirectionOppositeEndDirectionAndEqualAxis(long diffXDest, long diffYDest ){
 		return (diffXDest>0 && getOrientation()==3 
 				 || diffXDest<0 && getOrientation()==1 
 				 || diffYDest>0 && getOrientation()==0
 				 || diffYDest<0 && getOrientation()==2)
 				&&(diffYDest ==0 || diffXDest==0);
 		
 	}
 	
 	

	//TODO
	
	/**
	 * moves your robot next to the other robot
	 * 
	 * @param other
	 * 
	 *???
	 * 
	 * 
	 */

	public void moveNextToOther(Robot other){

	}

	/**
	 * 
	 * returns the position of the robot (x- & y-coordinates)
	 * 
	 * @return the x- & y-coordinate of the robot
	 * 			|result == this.coordinates
	 * 
	 */

	@Basic
	public long[] getPosition(){ //[WEGDOEN] we werken met long aangezien de max grootte v x en y long.MAX__VALUE
		return this.coordinates;

	}

	private long[] coordinates = new long [2]; //[WEGDOEN] met array ??

	/**
	 * sets the position on the field of the robot
	 * 	 * @throws OutOfBoardException
	 * 			The given coordinates exceed the borders of the board
	 * 			|!isValidPosition(xCoord, yCoord)
	 */

	private void setPosition(long xCoord, long yCoord) throws OutOfBoardException { // [WEGDOEN] setXCoord en y maken?
		if (!isValidPosition(xCoord, yCoord) == true){
			throw new OutOfBoardException();
		}
		this.coordinates[0] = xCoord;
		this.coordinates[1] = yCoord;
	}

	/**
	 * gets the x-coordinate of the position of the robot
	 * 
	 * @return the x-coordinate of the robot on the board
	 * 			| result == xCoord
	 */

	@Basic
	public long getXCoord(){
		return this.coordinates[0];
	}

	/**
	 * gets the y-coordinate of the position of the robot
	 * 
	 * @return  the y-coordinate of the robot on the board
	 * 			| result == yCoord
	 */

	@Basic
	public long getYCoord(){
		return this.coordinates[1];
	}

	//[WEGDOEN]DOCUMENTATIE NOG IN ORDE BRENGEN

	/**
	 * 
	 * checks whether the given position is not out of the bounds of the board
	 * 
	 * @param x
	 * 		the x-coordinate of the desired position
	 * @param y
	 * 		the y-coordinate of the desired position
	 * 
	 * @return returns true if the wanted position is on the board
	 * 			|result == ((xCoord <= X_MAX_VALUE) && (yCoord <= Y_MAX_VALUE) && (xCoord > X_MIN_BOARD) && (yCoord > Y_MIN_BOARD))
	 */

	public static boolean isValidPosition(long x, long y){
		return ((x <= X_MAX_VALUE) && (y <= Y_MAX_VALUE) && (x > X_MIN_BOARD) && (y > Y_MIN_BOARD));
	}

	private static final long X_MAX_VALUE = Long.MAX_VALUE;
	private static final long Y_MAX_VALUE = Long.MAX_VALUE;
	private static final long X_MIN_BOARD = 0;
	private static final long Y_MIN_BOARD = 0;

	//private long xCoord;
	//private long yCoord;

	//werken met enum ?

	//@Basic
	//public Orientation getOrientation(){
	//	 return this.orientation ;
	//}

	//private Orientation orientation;

	/**
	 * gets the orientation of the robot
	 * 
	 * @return the orienation the robot is facing
	 * 			| return this.orientation
	 */

	@Basic
	public int getOrientation(){
		return this.orientation;
	}

	private int orientation;

	/**
	 * sets the desired orientation of the robot
	 * 
	 * @param orientation
	 * 			the orientation the robot will be facing
	 * @post ...
	 * 		|new.orientation=...
	 * 
	 */

	private void setOrientation(int orientation){

		// NEGATIEF
		//if(orientation % 4 >= 0){

		//if(orientation == 0 || orientation == 1 || orientation == 2 || orientation == 3){
		if(orientation >= 0){
			this.orientation = (orientation % POSSIBLE_DIRECTIONS); // casting probleem?
		}
		else{
			this. orientation = (POSSIBLE_DIRECTIONS - Math.abs(orientation % POSSIBLE_DIRECTIONS));
		}
		//}
		//}


	}

	public static final int POSSIBLE_DIRECTIONS = 4;


//TODO setOrientation echt total maken d.w.z. foute input negeren en nieuwe functie maken (private) die nieuwe orientatie doorgeeft aan setter
}
